package Simulador.Hardware;

import Auxiliar.Fila;
import Auxiliar.FilaEventos;
import Simulador.Event.Evento;
import Simulador.Job.Segmento;
import java.util.LinkedList;

/**
 * Representa uma memória segmentada, nos moldes de uma memória particionada, 
 * com partições criadas conforme os segmentos são alocados/removidos.
 * A alocação é feita buscando o menor bloco onde o segmento pode ser alocado, 
 * e a remoção junta o novo bloco vazio com blocos vazios vizinhos, se houver,
 * formando um novo bloco
 * @author Fernando
 * @author Matheus
 */
public class Memoria extends Dispositivo {

    int tam;
    //int livre;
    LinkedList<BlocoMemoria> blocos = new LinkedList<BlocoMemoria>(); 

    /**
     * Creates a new instance of Memoria
     * @param tamanho 
     */
    public Memoria(int tamanho) {
        this.tam = tamanho;
        //this.livre = tamanho;
        blocos.add(new BlocoMemoria(tamanho,0,null));//cria um bloco sem segmentos alocados
        //imprimeMemoria("Inicialização");
        //imprimeMemoriaLivre();
        //imprimeMemoriaOcupada();
        fila = new Fila();
    }

    /**
     * Aloca memoria para o segmento que causou o evento E.
     * Caso nao exista memoria suficiente, coloca o processo na fila
     * @param E
     * Evento que causou a solicitacao
     * @return
     * um evento de requisicao de CPU caso a memoria tenha sido alocada
     * null caso nao exista espaco suficiente
     */
    @Override
    public Evento requerirRecurso(Evento E) {
        int indice=getBlocoSuficiente(E.getSegmento().getTamMemoria());
        if (indice >= 0) {
            int base = blocos.get(indice).getBase();
            blocos.add(indice,new BlocoMemoria(E.getSegmento().getTamMemoria(),base,E.getSegmento()));
            atualizaBlocoLivre(indice+1);//novo indice do bloco livre após a inserção do segmento é indice+1
            //imprimeMemoria("Alocação de segmento de tamanho "+E.getSegmento().getTamMemoria());
            //imprimeMemoriaLivre();
            //imprimeMemoriaOcupada();
            
            return new Evento(Evento.RequereCPU, E.getSegmento(), E.getMomentoDeChegada());
        } else {//indice==-1, que representa que não é possível alocar o segmento
            //se for fazer desfragmentação da memória, deve ser feito aqui e utilizar novamente getBlocoSuficiente()
            fila.addItem(E.getSegmento());
            System.out.println(" Entrou na Fila de Memoria.");
            return null;
        }
    }

    /**
     * TODO Caso caiba mais de um processo, adicionar todos.
     * O problema esta quase resolvido.
     * 
     * 
     * Libera memoria do segmento que causou o evento E.
     * Caso existam processos na fila, tenta alocar memoria para ele.
     * @param E
     * Evento que causou a solicitacao.
     * @return
     * um evento de requisicao de CPU caso a memoria tenha sido alocada para um
     * processo na fila.
     * null caso contrario.
     */
    @Override
    public Evento liberarRecurso(Evento E) {
        //livre += E.getProcesso().getTamMemoria();
        liberaBloco(E.getSegmento());
        //imprimeMemoria("LiberaÃ§Ã£o de bloco");
        if (!(fila.isEmpty())) {
            Fila f = fila;
            fila = new Fila();
            FilaEventos fe = new FilaEventos();
            Segmento k;
            while (!f.isEmpty()) {
                k = (Segmento) f.getItem();
                Evento e = requerirRecurso( E );
                if (e != null) {
                    fe.addItem(e);
                } else {
                    fila.addItem(k);
                }
            }
            //return fe;
            return null;
        }
        return null;
    }

    /**
     * 
     * @return
     * tamanho da memoria
     */
    public int tamanho() {
        return tam;
    }

    protected float tempoTransacao() {
        throw new UnsupportedOperationException("Not supported yet. And not used yet");
    }
    /**
     * Atualiza a base e o tamanho do bloco livre, após a realização da inserção
     * de um segmento. No caso do segmento ter tamanho nulo, ele é removido 
     * @param indice O indice do bloco livre
     */
    private void atualizaBlocoLivre(int indice) {
        blocos.get(indice).setBase(blocos.get(indice-1).getFim()+1);
        blocos.get(indice).setTamanho(blocos.get(indice).getTamanho()-blocos.get(indice-1).getTamanho());
        
        if(blocos.get(indice).getTamanho()==0){
            blocos.remove(indice);
        }
    }

    /**
     * Junta dois blocos vizinhos. Altero o tamanho do bloco de menor indice,
     * mantendo a sua base. Ao termino, o bloco de maior indice é removido.
     * @param indiceA indice do primeiro bloco
     * @param indiceB indice do segundo bloco
     */
    private void fundirBlocos(int indiceA, int indiceB) {
        int maior;
        int menor;
        int novoTamanho;
        if(indiceA-indiceB==1){
            maior=indiceA;
            menor=indiceB;
        }
        else if(indiceB-indiceA==1){
            maior=indiceB;
            menor=indiceA;
        }
        else{
            System.out.println("Erro: Os blocos não são vizinhos.");
            return;
        }
        //blocos.get(menor).limpa();//remove o segmento alocado, se houver
        novoTamanho=blocos.get(menor).getTamanho()+blocos.get(maior).getTamanho();
        blocos.get(menor).setTamanho(novoTamanho);
        blocos.remove(maior);
    }

   
    
    /**
     * Procura o indice do menor bloco necessário para alocar o segmento
     * @param tamanho O tamanho do segmento
     * @return O indice do bloco (ou -1, se não encontrou)
     */
    private int getBlocoSuficiente(int tamanho){
        int indice=-1;
        int deltaMenor=tam;
        int deltaAtual=tam;
        for(int i=0; i<blocos.size() && deltaMenor!=0; i++){
            if(blocos.get(i).getSegmento()==null){
                deltaAtual=tamanho-blocos.get(0).getTamanho();
                if(deltaAtual<=deltaMenor){
                    deltaMenor=deltaAtual;
                    indice=i;
                }
            }
            
        }
        return indice;
    }

    /**
     * Pega o indice do bloco onde o segmento está alocado
     * @param s O segmento cujo indice é procurado
     */
    private int getIndiceBloco(Segmento s) {
        for(int i=0; i<blocos.size();i++){
            if(blocos.get(i).getSegmento()== s)
                return i;
        }
        return -1;
    }
    /**
     * Remove um segmento da memória, juntando áreas livres vizinhas
     * @param processo O segmento a ser removido
     */
    private void liberaBloco(Segmento processo) {
        int indice = getIndiceBloco(processo);
        blocos.get(indice).limpa();
        if(indice>=0)
            if(blocos.get(indice+1).getSegmento()==null){
                fundirBlocos(indice,indice+1);//junta com o posterior
            }
        if(indice>0)
            if(blocos.get(indice-1).getSegmento()==null){
                fundirBlocos(indice,indice-1);//junta com o anterior
            }
           
                
        
    }
    /**
     * Imprime a memoria inteira
     * @param acao A ação que se esta sendo testada com a impressão
     */
    private void imprimeMemoria(String acao){
        String s;
        System.out.println("--------------Memoria--------------");
        System.out.println("Ação testada: "+acao);
        for(int i=0;i<blocos.size();i++){
            if(blocos.get(i).getSegmento()==null) s="vazio";
            else s="segmento: "+blocos.get(i).getSegmento().getId();
            System.out.println("Bloco: "+i+" base: "+blocos.get(i).getBase()+" fim: "+blocos.get(i).getFim()+" tam: "+blocos.get(i).getTamanho()+" "+s);
        }
        System.out.println("-----------------------------------");
    }
    
    /**
     * Imprime a memoria livre
     * @param acao A ação que se deseja testar
     */
    private void imprimeMemoriaLivre(){
        System.out.println("----------Memoria Livre--------------");
        for(int i=0;i<blocos.size();i++){
            if(blocos.get(i).getSegmento()==null) 
                System.out.println("Bloco: "+i+" base: "+blocos.get(i).getBase()+" fim: "+blocos.get(i).getFim()+" tam: "+blocos.get(i).getTamanho());
            
        }
        System.out.println("-------------------------------------");
    }
    
    /**
     * Imprime a memoria ocupada
     * @param acao A ação que se deseja testar
     */
    private void imprimeMemoriaOcupada(){
        String s;
        System.out.println("----------Memoria Ocupada------------");
        for(int i=0;i<blocos.size();i++){
            if(blocos.get(i).getSegmento()!=null){
                s="segmento: "+blocos.get(i).getSegmento().getId();
                System.out.println("Bloco: "+i+" base: "+blocos.get(i).getBase()+" fim: "+blocos.get(i).getFim()+" tam: "+blocos.get(i).getTamanho()+" "+s);
            }
        }
        System.out.println("-------------------------------------");
    }
}
